

/**
 * 
 * This class is one of the main components of the network communication and for that, uses the "Server Comm".
 * 
 * @version 1
 * 
 * @authors 
 * Tiago Pacheco - 30834
 * Stefanie Pinheiro - 35258
 * Pedro Gamelas - 35537
 * Ricardo Pereira - 38425
 * 
 */

import java.util.HashMap;
import java.util.LinkedList;

import javax.swing.JOptionPane;

public class Server implements BirdsongServer{
	private LinkedList<Client> onlineClients = new LinkedList<Client>();
	private HashMap<String, LinkedList<Client>> followersLists = new HashMap<String, LinkedList<Client>>();
	private ServerSideMessage message;
	private Client newClient;

	/**
	 * This public method is responsible for starting the Server, using serverCommunicator class to do that.
	 * It also does a lot of functions, like getting messages, follow/unfollow clients, etc
	 * @param serverComunicator - The ServerComm that the Server should use to connect to.
	 */
	@Override
	public void start(ServerComm serverComunicator) {
		serverComunicator.start();

		while (true) {
			message = serverComunicator.getNextMessage();

			if (!serverComunicator.getNextMessage().equals(null)) {
				if (message.getType() == ServerSideMessage.Type.CLIENT_CONNECTED) {

					serverResponseToClientConnectedMessage(serverComunicator);

				}else if (message.getType() == ServerSideMessage.Type.CLIENT_DISCONNECTED) {

					serverResponseToClientDisconnectedMessage(serverComunicator);

				}else if (message.getType() == ServerSideMessage.Type.BIRDSONG_MESSAGE) {

					serverResponseToBirdsongMessage(serverComunicator);

				}else if (message.getType() == ServerSideMessage.Type.FOLLOW) {

					serverResponseToFollowMessage(serverComunicator);

				}else if (message.getType() == ServerSideMessage.Type.UNFOLLOW) {

					serverResponseToUnfollowMessage(serverComunicator);

				}
			}else {
				break;
			}
		}
	}

	/**
	 * This private method is responsible for adding (connected) clients to the onlineClients List 
	 * and inform the other online clients.
	 * @param serverComunicator - The ServerComm that the Server should use to connect to.
	 */
	private void serverResponseToClientConnectedMessage(ServerComm serverComunicator) {
		if (serverComunicator.clientIsConnected(message.getClientNickname())) {
			addOnlineClientToTheList();

			for (Client c : onlineClients) {
				if (!c.getClientNickname().equals(newClient.getClientNickname())) {
					sendInfoAboutConnectedClient(serverComunicator, c);	
				}
			}
		}else {
			serverComunicator.sendError(message.getClientNickname(), "Something went wrong when trying to connect to the server! Try again later! ");
		}
	}

	/**
	 * This private method is responsible for sending information to all online clients regarding the new client connected. 
	 * It also informs the new client about who is online. 
	 * @param serverComunicator - The ServerComm that the Server should use to connect to.
	 * @param c - the online client who will receive and send notifications of his actual state (online).
	 */
	private void sendInfoAboutConnectedClient(ServerComm serverComunicator,Client c) {
		/* S4 ?? */	serverComunicator.sendClientConnected(newClient.getClientNickname(), c.getClientNickname());	
		/* S5 */	serverComunicator.sendClientConnected(c.getClientNickname(), newClient.getClientNickname());
	}

	/**
	 * It adds a new connected Client to the OnlineClientsList and creates a new list of respective followers.
	 */
	private void addOnlineClientToTheList() {
		newClient = new Client(message.getClientNickname());
		onlineClients.add(newClient);
		followersLists.put(newClient.getClientNickname(), new LinkedList<Client>());
	}

	/**
	 * This private method removes clients from the LinkedList "onlineClients". In other words, it disconnect clients from the Server.
	 * It also informs the other clients that this client is now disconnected and cleans the information about his followers.
	 * @param serverComunicator - The ServerComm that the Server should use to connect to.
	 */
	private void serverResponseToClientDisconnectedMessage(ServerComm serverComunicator) {
		for (Client c : onlineClients) {
			if (c.getClientNickname().equals(message.getClientNickname())) {
				disconnectClient(serverComunicator, c);
				JOptionPane.showMessageDialog(null, "Client removed successfully!");
				break;
			}
		}

		for (Client c : onlineClients) {
			sendInfoAboutDisconnectedClient(serverComunicator, c);
		}
	}

	/**
	 * This private method is responsible for disconnecting the client by removing him from the list of online 
	 * clients and removing his list of followers. When all its done, the client must be disconnected from the server.
	 * @param serverComunicator - The ServerComm that the Server should use to connect to.
	 * @param c - the online client who will be disconnected from the server.
	 */
	private void disconnectClient(ServerComm serverComunicator, Client c) {
		onlineClients.remove(c.getClientNickname());
		followersLists.remove(message.getClientNickname());
		serverComunicator.disconnectClient(message.getClientNickname());
	}

	/**
	 * This private method is should inform online clients that a specific client was disconnected.
	 * @param serverComunicator - The ServerComm that the Server should use to connect to.
	 * @param c - the online client who will receive the notification of the disconnected client.
	 */
	private void sendInfoAboutDisconnectedClient(ServerComm serverComunicator,
			Client c) {
		/* S5 */   serverComunicator.sendClientDisconnected(c.getClientNickname(), message.getClientNickname());
	}

	private void serverResponseToFollowMessage(ServerComm serverComunicator) {
		for (Client c : onlineClients) {
			if (c.getClientNickname().equals(message.getClientNickname())) {
				boolean alreadyFollower = false;
				for (Client c2 : followersLists.get(message.getPayload())) {
					if (c2.equals(message.getClientNickname())) {
						serverComunicator.sendError(message.getClientNickname(), "You are already following this client!");
						alreadyFollower = true;
						break;
					}
				}
				if (!alreadyFollower) {
					addNewFollower(serverComunicator, c);	
				}
			}
		}
	}

	private void addNewFollower(ServerComm serverComunicator, Client c) {
		/* S3 */ serverComunicator.sendNewFollower(message.getPayload(), message.getClientNickname());
		followersLists.get(message.getPayload()).add(c);
	}

	private void serverResponseToUnfollowMessage(ServerComm serverComunicator) {
		boolean canUnfollow = false;
		for (Client c : followersLists.get(message.getPayload())) {
			if (c.equals(message.getClientNickname())) {
				removeFollower(serverComunicator, c);
				canUnfollow = true;
				break;
			}
		}
		if (!canUnfollow) {
			serverComunicator.sendError(message.getClientNickname(), "You are not following this client!");
		}

	}

	private void removeFollower(ServerComm serverComunicator, Client c) {
		/* S3 */ serverComunicator.sendNewUnfollow(message.getPayload(), message.getClientNickname());
		followersLists.get(message.getPayload()).remove(c);
	}

	private void serverResponseToBirdsongMessage(ServerComm serverComunicator) {
		for (Client c : followersLists.get(message.getClientNickname())) {
			sendMessageTofollowers(serverComunicator, c);
		}
	}

	private void sendMessageTofollowers(ServerComm serverComunicator, Client c) {
		serverComunicator.sendBirdsongMessage(message.getClientNickname(), c.getClientNickname() , message.getClientNickname() +" said: " + message.getPayload());
	}

	/**
	 * @return the onlineClients List.
	 */
	public LinkedList<Client> getOnlineClients() {
		return onlineClients;
	}

	/**
	 * 
	 * @param message
	 */
	public void setMessage(ServerSideMessage message) {
		this.message = message;
	}

	/**
	 * @return the server side message
	 */
	public ServerSideMessage getMessage() {
		return message;
	}

	/**
	 * @return the new Client.
	 */
	public Client getNewClient() {
		return newClient;
	}

	public static void main(String[] args) {
		TestServerComunicator testServerComunicator = new TestServerComunicator();
		Server s = new Server();
		s.start(testServerComunicator);
	}

}